//
//  ScanProducer.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 31/03/2020.
//  Copyright 2020 Thomas Harte. All rights reserved.
//

#pragma once

#include "Outputs/ScanTarget.hpp"
#include "Configurable/StandardOptions.hpp"

#include "TimedMachine.hpp"

namespace MachineTypes {

/*!
	A ScanProducer::Producer is any machine that produces video output of the form accepted
	by a ScanTarget.
*/
class ScanProducer {
public:
	/*!
		Causes the machine to set up its display and, if it has one, speaker.

		The @c scan_target will receive all video output; the caller guarantees
		that it is non-null.
	*/
	virtual void set_scan_target(Outputs::Display::ScanTarget *) = 0;

	/*!
		@returns The current scan status.
	*/
	virtual Outputs::Display::ScanStatus get_scan_status() const {
		// There's an implicit assumption here that anything which produces scans
		// is also a timed machine. And, also, that this function will be called infrequently.
		const TimedMachine *timed_machine = dynamic_cast<const TimedMachine *>(this);
		return get_scaled_scan_status() / float(timed_machine->get_clock_rate());
	}

protected:
	virtual Outputs::Display::ScanStatus get_scaled_scan_status() const {
		// This deliberately sets up an infinite loop if the user hasn't
		// overridden at least one of this or get_scan_status.
		//
		// Most likely you want to override this, and let the base class
		// throw in a divide-by-clock-rate at the end for you.
		return get_scan_status();
	}

	/*!
		Maps from Configurable::Display to Outputs::Display::VideoSignal and calls
		@c set_display_type with the result.
	*/
	void set_video_signal_configurable(const Configurable::Display type) {
		Outputs::Display::DisplayType display_type;
		switch(type) {
			default:
			case Configurable::Display::RGB:
				display_type = Outputs::Display::DisplayType::RGB;
			break;
			case Configurable::Display::SVideo:
				display_type = Outputs::Display::DisplayType::SVideo;
			break;
			case Configurable::Display::CompositeColour:
				display_type = Outputs::Display::DisplayType::CompositeColour;
			break;
			case Configurable::Display::CompositeMonochrome:
				display_type = Outputs::Display::DisplayType::CompositeMonochrome;
			break;
		}
		set_display_type(display_type);
	}

	/*!
		Maps back from Outputs::Display::VideoSignal to Configurable::Display,
		calling @c get_display_type for the input.
	*/
	Configurable::Display get_video_signal_configurable() const {
		switch(get_display_type()) {
			default:
			case Outputs::Display::DisplayType::RGB:					return Configurable::Display::RGB;
			case Outputs::Display::DisplayType::SVideo:					return Configurable::Display::SVideo;
			case Outputs::Display::DisplayType::CompositeColour:		return Configurable::Display::CompositeColour;
			case Outputs::Display::DisplayType::CompositeMonochrome:	return Configurable::Display::CompositeMonochrome;
		}
	}

	/*!
		Sets the display type.
	*/
	virtual void set_display_type(Outputs::Display::DisplayType) {}

	/*!
		Gets the display type.
	*/
	virtual Outputs::Display::DisplayType get_display_type() const { return Outputs::Display::DisplayType::RGB; }
};

}
